<?

/**
 * Klasa ModelCollection - implementuje interfejs systemowy Iterator
 * Sluzy do przechowywania kolekcji obiektow danego modelu
 * 
 * @param ModelMapper $mapper - obiekt mappera modelu
 * @param ADORecordSet $result - obiekt ADORecordSet
 * @param integer $total - przechowuje informacje o maksymalnej liczbie przechowywanych obiektow
 * @param integer $pointer - wskaznik na aktualny obiekt kolekcji
 * @param array $objects - Obiekty modelu trzymane w kolekcji
 * @param array $raw - pojedyncze wiersze tabeli, z ktorych tworzone sa obiekty na zadanie 
 * - jesli obiekt nie zostal utworzony i nie znajduje sie w tablicy $objects 
 * to jest tworzony na podstawie danych z tablicy $raw
 *
 */
abstract class ModelCollection implements ArrayAccess, Countable, Iterator{
	private $mapper;
	private $result;
	private $total = 0;
	private $pointer = 0;
	protected $objects = array();
	protected $raw = array();
	
	/**
	 * Konstruktor kolekcji - jesli $result = null i $mapper = null, kolekcja jest pusta
	 * jesli jest $result i $mapper - konstruktor buduje - za pomoca metody init_db - 
	 * kolekcje obiektow na podstawie przekazanych danych
	 *
	 * @param ADORecordSet $result
	 * @param ModelMapper $mapper
	 */
	function __construct($result = null, $mapper = null){
		if ($result && $mapper){
			$this->init_db($result, $mapper);
		}
	}
	
	/**
	 * Metoda init_db - na podstawie przekazanych danych buduje mape $raw - na ktorej podstawie beda tworzone obiekty modelu
	 *
	 * @param ADORecordSet $result
	 * @param ModelMapper $mapper
	 */
	protected function init_db(ADORecordSet $result, ModelMapper $mapper){
		$this->result = $result;
		$this->mapper = $mapper;
		$this->total += $this->result->NumRows();
		while ($row = $this->result->FetchRow()) {
			$this->raw[] = $row;
		}
	}
	
	/**
	 * Metoda doAdd - przyjmuje jako parametr obiekt DomainModel - sluzy do dodawania kolejnego obiektu do kolekcji
	 *
	 * @param DomainModel $object
	 */
	protected function doAdd(DomainModel $object){
		$this->notifyAccess();
		$this->objects[$this->total] = $object;
		$this->total++;
	}
	
	/**
	 * Metoda notifyAccess - metoda pusta - do rozwiniecia w klasach pochodnych - Wzorzec LazyLoad
	 *
	 */
	protected function notifyAccess(){
		
	}
	
	/**
	 * Metoda getObjectAt - na podstawie parametru $num - poszukuje w kolekcji obiektu o podanym identyfikatorze
	 * w pierwszej fazie sprawdza tablice obiektow, jesli jej tam nie znajdzie szuka w tablicy $raw, jak odnajdzie w tablicy $raw
	 * to tworzy egzemplarz modelu, dodaje go do tablicy $objects i zwraca
	 *
	 * @param int $num
	 * @return DomainModel $obj
	 */
	private function getObjectAt($num){
		$this->notifyAccess();
		
		if ($num >= $this->total || $num < 0){
			return null;
		}
		
		if (isset($this->objects[$num])){
			return $this->objects[$num];
		}
		
		if (isset($this->raw[$num])){
			
		
			$this->objects[$num] = $this->mapper->loadArray($this->raw[$num]);
			
			return $this->objects[$num];
		}
	}
	
	/**
	 * Metoda rewind - implementacja interfejsu Iterator
	 * Ustawia wskaźnik na pierwszy element kolekcji
	 */
	public function rewind(){
		$this->pointer = 0;
	}
	
	/**
	 * Metoda current - implementacja interfejsu Iterator
	 * Zwraca obiekt kolekcji na ktory wskazuje wskaznik
	 * @return DomainModel $obj
	 */
	public function current(){
		$object = $this->getObjectAt($this->pointer);
		return $object;
	}
	
	/**
	 * Metoda key - implementacja interfejsu Iterator
	 * zwraca wartosc wskaznika $pointer
	 *
	 * @return integer $pointer
	 */
	public function key(){
		return $this->pointer;
	}
	
	/**
	 * Metoda next - implementacja interfejsu Iterator
	 * Zwraca kolejny obiekt kolekcji i przestawia wartosc wskaznika
	 *
	 * @return DomainModel $obj
	 */
	public function next(){
		$this->notifyAccess();
		$row = $this->getObjectAt($this->pointer);
		
		if ($row) {
			$this->pointer++;
		}
		
		return $row;
	}
	
	/**
	 * Metoda valid - implementacja interfjesu Iterator
	 * Zwraca wartosc boolean - sprawdza czy aktualny element kolekcji jest poprawny
	 *
	 * @return boolean
	 */
	public function valid(){
		return (!is_null($this->current()));
	}
	
	public function reverse(){
		$this->notifyAccess();
		$this->raw = array_reverse($this->raw);
	}
	public function count(){
		$this->notifyAccess();
		return count($this->raw);
	}

	public function getLast(){
		$this->notifyAccess();
		return $this->getObjectAt($this->total);
	}
	
	/**
	 * Metoda toArray - przetwarza wszystkie obiekty kolekcji do tablicy assocjacyjnej
	 *
	 * @return array $array
	 */
	public function toArray(){
		if ($this->count() > 0)
			foreach ($this as $v){
				$array[] = $v->toArray();
			}
		else
			$array = null;
		return $array;
	}
	
	/**
	 * Metoda slice - wycina fragment kolekcji, w oparciu o funkcje array_slice
	 *
	 * @param integer $offset
	 * @param integer $length
	 * @return return
	 */
	public function slice($offset, $length=null) {
		$this->notifyAccess();
		$n = count($this->raw);
		$collection = clone $this;
		$collection->raw = array_slice($this->raw, $offset, $length);
		return $collection;
	}
	
	public function offsetSet($offset, $value) {
        $this->objects[$offset] = $value;
    }
    public function offsetExists($offset) {
        return isset($this->objects[$offset]);
    }
    public function offsetUnset($offset) {
        unset($this->objects[$offset]);
    }
    public function offsetGet($offset) {
        return isset($this->objects[$offset]) ? $this->objects[$offset] : null;
    }
}

?>